package service

import (
	"encoding/json"
	"fmt"
	"gitee.com/wangqiOvO/mapper-go-sdk/internal/clients"
	"gitee.com/wangqiOvO/mapper-go-sdk/internal/common"
	"gitee.com/wangqiOvO/mapper-go-sdk/internal/config"
	"gitee.com/wangqiOvO/mapper-go-sdk/internal/configmap"
	"gitee.com/wangqiOvO/mapper-go-sdk/internal/controller"
	"gitee.com/wangqiOvO/mapper-go-sdk/internal/di"
	"gitee.com/wangqiOvO/mapper-go-sdk/internal/event"
	"gitee.com/wangqiOvO/mapper-go-sdk/pkg/models"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"k8s.io/klog/v2"
	"os"
	"os/signal"
	"regexp"
	"sync"
	"time"
)

var (
	ms *MapperService
)

// MapperService the structure of the variable required by the mapper
type MapperService struct {
	ServiceName     string
	deviceInstances map[string]*configmap.DeviceInstance
	deviceModels    map[string]configmap.DeviceModel
	protocol        map[string]configmap.Protocol
	driver          models.ProtocolDriver
	initialized     bool
	dic             *di.Container
	wg              *sync.WaitGroup
	mqttClient clients.MqttClient
	mutex *sync.Mutex
	quit chan os.Signal
	httpClient clients.HttpClient
}



// MapperServiceInit initialize the mapperService config.
func (ms *MapperService)MapperServiceInit(serviceName string ,c config.Config,deviceInterface interface{}) error {
	if err := c.Parse(); err != nil {
		klog.Error("please use --config-file to point the location of file path")
		klog.Fatal(err)
		os.Exit(1)
	}
	if serviceName == "" {
		_, _ = fmt.Fprintf(os.Stderr, "Please specify device service name")
		os.Exit(1)
	}
	ms.ServiceName = serviceName
	ms.deviceInstances = make(map[string]*configmap.DeviceInstance)
	ms.deviceModels = make(map[string]configmap.DeviceModel)
	ms.protocol = make(map[string]configmap.Protocol)
	ms.dic = di.NewContainer(nil)
	ms.wg = new(sync.WaitGroup)
	ms.mutex = new(sync.Mutex)
	ms.quit = make(chan os.Signal)
	if driver,ok := deviceInterface.(models.ProtocolDriver);!ok{
		_,_ = fmt.Fprintf(os.Stderr, "Please specify device interface")
		os.Exit(1)
	}else{
		ms.driver = driver
	}
	signal.Notify(ms.quit, os.Interrupt)
	ms.waitExit()
	ms.mqttClient = clients.MqttClient{
			IP: c.Mqtt.ServerAddress,
			User:       c.Mqtt.Username,
			Passwd:     c.Mqtt.Password,
			Cert:       c.Mqtt.Cert,
			PrivateKey: c.Mqtt.PrivateKey}
	ms.httpClient = clients.HttpClient{
		IP: "0.0.0.0",
		Port: "8989",
		WriteTimeout: 5*time.Second,
		ReadTimeout: 5*time.Second,
	}
	if err := ms.mqttClient.Connect();err != nil{
		klog.Fatal(err)
		os.Exit(1)
	}
	if err := ms.httpClient.InitHttpClient(ms.wg);err != nil{
		klog.Fatal(err)
		os.Exit(1)
	}
	return configmap.Parse(c.Configmap, ms.deviceInstances, ms.deviceModels , ms.protocol)
}

// waitExit create a goroutine to monitor exit signal
func (ms *MapperService) waitExit(){
	go func() {
		<-ms.quit
		err := ms.driver.StopDevice()
		if err != nil {
			klog.Error(err)
			os.Exit(1)
		}
		klog.V(1).Info("Exit Mapper")
		os.Exit(1)
	}()
}

// initSubscribeMqtt subscribe the TwinUpdateDelta topic
func (ms *MapperService)initSubscribeMqtt() error{
	for k,_ := range ms.deviceInstances{
		topic := fmt.Sprintf(common.TopicTwinUpdateDelta, k)
		err := ms.mqttClient.Subscribe(topic, onMessage)
		if err != nil {
			return err
		}
		klog.V(1).Infof("Event %s is Listening....\n",k)
	}
	return nil
}

// onMessage callback function of Mqtt subscribe message.
func onMessage(client mqtt.Client, message mqtt.Message) {
	re := regexp.MustCompile(`hw/events/device/(.+)/twin/update/delta`)
	instanceID := re.FindStringSubmatch(message.Topic())[1]
	var delta event.DeviceTwinDelta
	if err := json.Unmarshal(message.Payload(), &delta); err != nil {
		//klog.Errorf("Unmarshal message failed: %v", err)
		return
	}
	for twinName, twinValue := range delta.Delta{
		i := 0
		for i = 0; i < len(ms.deviceInstances[instanceID].Twins); i++ {
			if twinName == ms.deviceInstances[instanceID].Twins[i].PropertyName {
				break
			}
		}
		if i == len(ms.deviceInstances[instanceID].Twins) {
			continue
		}
		// Desired value is not changed.
		if ms.deviceInstances[instanceID].Twins[i].Desired.Value == twinValue {
			continue
		}
		klog.V(4).Infof("Set %s:%s value to %s",instanceID,twinName,twinValue)
		ms.deviceInstances[instanceID].Twins[i].Desired.Value = twinValue
		err := controller.SetVisitor(instanceID, ms.deviceInstances[instanceID].Twins[i], ms.driver, ms.mutex, ms.dic)
		if err != nil {
			klog.Error(err)
			return
		}
	}
}